- 想要理解整个认证过程,必须要动手走一轮代码。
- 想要理解整个认证过程,必须要动手走一轮代码。
- 想要理解整个认证过程,必须要动手走一轮代码。
- 想要理解整个认证过程,必须要动手走一轮代码。
- 想要理解整个认证过程,必须要动手走一轮代码。
- 想要理解整个认证过程,必须要动手走一轮代码。
该文章在星球里吃了几个月的灰,后来看到 SleevedKey 在网上已经可以随便搜到,所以就索性放出来了。
已先投稿先知社区,链接为:https://xz.aliyun.com/t/8557
0x00 前言
这里提供了 CS 4.0 的认证过程,个人认为非常详细,文中配备认证的流程图,可以结合文中的代码注释,外加自己的 IDEA 调试,可以完整理解整个过程。因为 4.0 与 4.1 差了一个关键 key(前期的处理方式也多了一个步骤),因此这里就只针对 4.0 版本的认证进行说明。 附件中提供了 CSHook.jar
,是针对 CS 4.1 版本的,并且文章中也明确提供了适用于 CS 4.1 的完整 key(使用该 key 需要删除多余的步骤,直接使用 4.0 的验证)。
很多人拿到原版之所以没有搞破解,是因为缺少了最重要的 Sleeved 解密 key。 其实,到了 4.X 版本,是没有办法进行”破解”的,因为 AES 的密钥是无法进行破译
,所以 Sleeved 解密 key 只能等好心人提供。
CobaltStrike 4.X 的认证,如果对 Java 及密码学相关有所了解,理解起来并不难。但是对于它的破解来说,需要一个针对 Sleeved 模块的认证 key,这个 key 是无法进行穷举的,除非想不开了。因此对于破解来说,与其说破解,还不如说是将 key 进行补全了。
0x01 准备工作
1.1、必备知识
1.1.1、RAS 算法之加密与签名的区别
加密和签名都是为了安全性考虑,但略有不同。常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改。这里举 2 个例子说明。
- 第一个场景:战场上,B 要给 A 传递一条消息,内容为某一指令。
RSA 的加密过程如下:
1 | (1)A 生成一对密钥(公钥和私钥),私钥不公开,A 自己保留。公钥为公开的,任何人可以获取。 |
在这个过程中,只有 2 次传递过程,第一次是 A 传递公钥给 B,第二次是 B 传递加密消息给 A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。
- 第二个场景:A 收到 B 发的消息后,需要进行回复“收到”。
RSA 签名的过程如下:
1 | (1)A 生成一对密钥(公钥和私钥),私钥不公开,A 自己保留。公钥为公开的,任何人可以获取。 |
在这个过程中,只有 2 次传递过程,第一次是 A 传递加签的消息和消息本身给 B,第二次是 B 获取 A 的公钥,即使都被敌方截获,也没有危险性,因为只有 A 的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给 B,防止了消息内容的篡改。
但是,综合两个场景你会发现,第一个场景虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给 A。第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥验签来获得,并不能防止泄露。所以在实际应用中,要根据情况使用,也可以同时使用加密和签名,比如 A 和 B 都有一套自己的公钥和私钥,当 A 要给 B 发送消息时,先用 B 的公钥对消息加密,再对加密的消息使用 A 的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。
总结:公钥加密、私钥解密;私钥签名、公钥验签。
但是,有一个要注意的是:
1 | 当你用公钥加密的时候,需要用私钥解密。 |
1.1.2、HMAC 消息摘要算法
MAC,全称 Message Authentication Code
,也称为消息认证码(带密钥的Hash函数),通信实体双方使用的一种验证机制,保证消息数据完整性的一种工具。
在发送数据之前,发送方首先使用通信双方协商好的散列函数计算其摘要值。在双方共享的会话密钥作用下,由摘要值获得消息验证码。之后,它和数据一起被发送。接收方收到报文后,首先利用会话密钥还原摘要值,同时利用散列函数在本地计算所收到数据的摘要值,并将这两个数据进行比对。若两者相等,则报文通过认证。
说白了就是计算摘要的时候,需要一个秘钥 key,没有秘钥 key 就无法计算
1.1.3、AES
- 破解 AES 算法需要多长时间?
以 AES-128 算法为例,平均需要尝试 2^127 ≈ 1.7*10^38 个 128bit 的随机数作为密钥进行加解密运算,方能找到正确的密钥。
常言道,“天下武功,唯快不破”;反之,天下密码,快必可破。问题是,那得有多快?我们知道,比特币网络在全球范围内调用了非常庞大的硬件资源以达到极高的运算效率,每秒钟操作的 Hash 运算(SHA-256)可高达 2.5644*10^19次。虽然 AES 和 SHA-256 算法并不相同,运算量也有所差异,但我们不妨近似地用该数据估算全球人民众志成城破解 AES 算法所需要的时间。
假设 AES 的运算效率为 2.564410^19 ≈ 2^64.4753 次/秒,则进行 2^127 次 AES 运算所需要的时间为:
2^127 / 2^64.4753 ≈ 2^62.5247秒 ≈ 6.6345 10^18 秒 ≈ 1.8429 10^15 小时 ≈ 7.6789 10^13 天 ≈ 2.104 * 10^11年 ≈ 210,400,000,000 年
1.2、运行环境
此次破解测试使用的工具及文件为:
IntelliJ IDEA Community Edition 2020.1.4
Feb 22, 2020 - Cobalt Strike 4.0
使用过 IDEA 的朋友都知道,它具备反编译 Jar 包的能力。
首先,我们使用 IDEA 新建一个工程,将原始 Jar 包作为依赖进行导入,如下图所示:
此时 IDEA 将调用反编译模块,因此我们可以直接查看 jar 的源码,如图所示:
但由于单个文件点击,并不利于我们的有效查看,因此可以提取 IDEA 的反编译功能,用于对原始 Jar 包的反编译。
下面我们进行测试,IDEA 的反编译功能依赖于 java-decompiler.jar
,该文件存在于以下路径当中:
1 | %IDEA安装目录%\plugins\java-decompiler\lib\java-decompiler.jar |
其使用方法为:
1 | java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true c:\my.jar d:\decompiled\ |
将反编译后的 Jar 包进行解压,将解压的文件(带文件夹)放入 src
(该步骤仅将需要更改的文件放入即可,当然,全部放也没关系),文件夹内,如下图所示:
然后就是设置编译生成 Jar 的步骤。
在 Main Class
中填写 aggressor.Aggressor
,其余默认即可;然后尝试 Build Artifacts...
,正常情况下,则生成一个新的 Jar 包。
最后,为了方便实时预览及调试,我们需要对 Run
进行简单设置。
1 | 1.新建一个 run 配置 |
实践一下,是否配置都正常。出现以下信息就说明可行。
这部分内容不理解的朋友,可以去看看红队学院(知识星球)相关视频:RedCore 红队学院 CSTips
0x02 CS 3.X 版本的认证过程
其实,我们可以从头开始走一轮认证代码,3.X 相对简单,走下来其实不难。
主要涉及的文件:
1 | common/License // license 检查逻辑 |
先粗略说一下 3.X 的 .auth 整个加密过程是:
1 | 先对文本进行压缩,转换为 byte |
故此解密的话只需要逆向此流程即可,那么我们要伪造一个自己的授权文件的话,只需要把公钥替换为自己的,然后使用自己的私钥对文本内容进行加密即可。因为只有在验证 GUI 和 Console 的时候需要进行验证步骤,因此也可以直接写死 isValid()
、isPerpetual()
和 isAlmostExpired()
的值。比如:
1 | public Authorization() { |
4.0 相比于 3.14 版本,多了一轮新的验证及更为复杂。
0x03 CS 4 .X 版本的认证过程
之所以只说这个 CS 4.x 的认证过程,是因为该认证是在 3.X 的基础上进行改进的。
首先从主函数开始查看,第一步认证:License.checkLicenseGUI(new Authorization());
,我们在查看源码过程中,直接对源码进行注释即可。
1 | public class Aggressor { |
3.1、checkLicenseGUI()
该函数是 CobaltStrike 的第一道验证,主要检查授权文件是否存在、解析的数据是否正确。
1 | public static void checkLicenseGUI(Authorization var0) { |
首先是对 GUI 的一个验证,该验证有且仅有一次验证;它是调用 Authorization
类 中的 isValid()
、isPerpetual()
和 isAlmostExpired()
进行校验。
1 | isValid() // 判断文件是否存在、有效,格式是否正确等,isValid函数是一个flag,默认为false |
而它们都依赖于 Authorization.Authorization()
,因此我们需要先对 Authorization()
进行分析。
3.2、Authorization() 的分析过程
在 Authorization
类中,我们只需要查看 Authorization()
函数即可。
该函数主要解析授权码字段构成和有效期的计算,并且调用了 AuthCrypto
类的 decrypt
函数对文件进行解密,详细信息请看代码注释。
1 | public Authorization() { |
3.3、AuthCrypto 类
看看对 authkey.pub
及 cobaltstrike.auth
解密的类。
1 | /* |
结合上述两个代码,如果验证通过,则可以打开客户端页面。这与 3.X 的认证大致相同。
3.4、SleevedResource 类
与 3.X 不同的是,4.X 在 Authorization()
中新增了一个新的验证 SleevedResource.Setup()
。该验证的大致流程为:
1 | 1、使用 .auth 文件的一部分数据作为一个 key,将该 key 再进行处理拆分; |
跟进 SleevedResource.Setup(arrayOfByte3);
看一看:
1 | public class SleevedResource { |
发现调用 SleevedResource
类的构造函数并将该 byte 数组传递给了 dns.SleeveSecurity
的 registerKey()
方法,继续跟进该方法:
1 | public void registerKey(byte[] paramArrayOfByte) { |
嗯,到这里没见到往下走的验证了,估摸着第一轮验证就结束了。
此时启动 teamserver
,则会在 temserver
中看到一个错误:
1 | [on xxxxx byte message from resource ] [Sleeve] Bad HMAC |
注意:此验证是在调用 CS 内置 EXE/DLL
时所需要的,当验证不通过时,则出现该错误。因此当在绕过了开头的限制,则可以开启客户端,只不过功能受影响;只有该验证顺利通过,才是完全授权验证。
3.4、decrypt() 方法调用链
我们搜索该错误,在 SleeveSecurity.decrypt()
中找到。
我们依次查看调用链:
我们在 SleevedResource
中看到此 decrypt()
方法的调用。而 readResource()
则调用了 _readResource()
。我们再查看关于 readResource()
的调用:
我们就此挑选比较干净的调用例子来分析。
1 | protected byte[] export_dll() { |
此时回头查看 _readResource()
函数。
1 | /* |
我们重点来看这个解密。
1 | /* |
这个方法要在初始验证阶段是不会进行调用的,为了方便调试,直接在 registerKey()
写个调用即可,比如:
我们只是捋清整个需要认证的过程,不细讨其他的东西,因此只需要知道 cobaltstrike.auth
文件的组成和用处即可。
3.5、认证流程图
0x04 破解方法
理论上,穷举 Authorization()
中的 arrayOfByte3
有些不现实;因为逆着推回去,需要推出 hmac 的 key,AES 的 key,是我想多了。
因此,至少需要知道 arrayOfByte3
的值才可能正常运行成功。但在有这个关键 key 的前提下,我们可以这么做。
4.1、重新生成 license
也就是说,我们要伪造一个自己的授权文件的话, 只需要生成自己的 RSA 公私钥,然后使用私钥对文本内容进行加密,将公钥保存成 authpub.key
,并计算 MD5 值,对 AuthCrypto.class
中的 8bb4df00c120881a1945a43e2bb2379e
进行替换即可。这里的做法就是 Cobaltstrike 4破解之 我自己给我自己颁发license 中的做法。
4.1.1、.auth 文件组成
由对 Authorization()
的分析过程可以得出文本内容应该由这些有效元素构成:
1 | 将 .auth 文件读取成 byte[],处理之后得出 26 位的 byte[],将其拆分为: |
因此我们只需要逆推 DataParser
中的 readInt()
就可以得到想要的内容。
因此在解析 .auth
后返回的 byte[]
应该为:
1 | byte[] decrypt = { 1, -55, -61, 127, 0, 0, 34, -112, 127, 16, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6 }; |
代入测试:
4.1.2、生成 RSA 公钥、私钥及签名
该步骤,主要是生成 RSA 公私钥,然后使用私钥对上述生成的数据进行加密(注意,当你用私钥加密的时候,需要用公钥解密)后保存到 cobaltstrike.auth
中。
你可以使用 openssl
生成,使用 2048 位即可,也可以使用代码生成,参考代码如下:
1 | import javax.crypto.BadPaddingException; |
成功生成,剩下的就是替换相关文件,再更改 AuthCrypto.load()
中的 MD5 值。
4.2、硬编码 key
直接在 Authorization()
中注释掉以下代码行:
1 | byte[] arrayOfByte2 = authCrypto.decrypt(arrayOfByte1); |
然后直接将解析后的 byte[] 进行写入
1 | byte[] arrayOfByte2 = { 1, -55, -61, 127, 0, 0, 34, -112, 127, 16, 27, -27, -66, 82, -58, 37, 92, 51, 85, -114, -118, 28, -74, 103, -53, 6 }; |
4.3、CSHook.jar
以上两种方法都对 Jar 包进行修改,那我们再来看看不对源码进行修改的前提下进行 hook。Hook 的原理就是热替换,热替换的核心就在于 Instrumentation
的两个方法:
1 | // addTransformer() 用来注册类的修改器; |
这里主要是使用了 addTransformer()
,其实原理很简单,就是将 4.2 中编译好的 Authorization()
类进行热替换,从而不去修改 jar 包的情况下完成认证。这部分知识可以参考:javaagent使用指南
4.3.1、读取 Authorization.class
首先先读取改写好的 Authorization.class
:
1 | // 先读取 Authorization.class,byte[] 转 base64 |
然后再编写 addTransformer()
的调用类
4.3.2、Transformer 类
1 | public class Transformer implements ClassFileTransformer { |
4.3.3、premain
1 | public class CSHook { |
注意:指定 premain
方法的位置,这里选择了修改 META-INF/MANIFEST.MF
的内容,将 Main-Class
修改成 Premain-Class
。编译生成即可。
最后,这里提供 CS 4.1 的 key :
1 | byte[] arrayOfByte2 = { 1, -55, -61, 127, 0, 0, 34, -112, 127, 16, -128, -29, 42, 116, 32, 96, -72, -124, 65, -101, -96, -63, 113, -55, -86, 118 }; |